/*
 * www.pconline.com.cn - 1997-2010.
 */
package cn.pconline.activity.repository;

import cn.pconline.activity.cache.CacheClient;
import cn.pconline.activity.util.BaseConfig;
import cn.pconline.activity.util.JsonUtils;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;

/**
 *
 * @author xhchen
 */
public class GenericRepository {

    SimpleJdbcTemplate simpleJdbcTemplate;
    CacheClient cacheClient;
    IdGenerator idGenerator;
    String database = (String)BaseConfig.get("database");
    String tablePreFix = (String)BaseConfig.get("tablePreFix")+"_";
    ORMHelper orm = new ORMHelper(tablePreFix);
    MySQLPageBuilder mySQLPageBuilder = new MySQLPageBuilder();
    OraclePageBuilder oraclePageBuilder = new OraclePageBuilder();

    public void setCacheClient(CacheClient cacheClient) {
        this.cacheClient = cacheClient;
    }

    public void setSimpleJdbcTemplate(SimpleJdbcTemplate simpleJdbcTemplate) {
        this.simpleJdbcTemplate = simpleJdbcTemplate;
    }

    public void setIdGenerator(IdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }

    public void setDatabase(String database) {
        this.database = database;
    }

    public String getTablePreFix() {
        return tablePreFix;
    }

    public <T> T find(long id, Class<T> type) {
        T obj = null;

        boolean needCache = orm.needCache(type);
        if (needCache) {
            obj = findFromCache(id, type);
        }

        if (obj == null) {
            obj = findFromDb(id, type);
            if (needCache) {
                putToCache(obj, type, id);
            }
        }
        return obj;
    }

    public long getObjectId(Object obj) {
        long id = 0;
        try {
            id = orm.getObjectId(obj);
        } catch (Exception e) {
            throw new RuntimeException("get id of object error", e);
        }
        return id;
    }

    public long create(Object obj) {
        String table = orm.getTableName(obj.getClass());
        String key = orm.getKey(obj.getClass());
        long id = getObjectId(obj);
        if (id == 0) {//
            id = idGenerator.generate(table, key);
        }
        simpleJdbcTemplate.update(orm.getCreateSql(obj.getClass(), id), new BeanPropertySqlParameterSource(obj));
        return id;
    }

    public void delete(Object obj) {
        simpleJdbcTemplate.update(orm.getDeleteSql(obj.getClass()), new BeanPropertySqlParameterSource(obj));
        if (orm.needCache(obj.getClass())) {
            removeFromCache(obj, getObjectId(obj));
        }
    }

    public <T> void delete(long id, Class<T> type) {
        String sql = "delete from " + orm.getTableName(type) + " where " + orm.getKey(type) + " = ?";
        simpleJdbcTemplate.update(sql, id);
        if (orm.needCache(type)) {
            String key = type.getSimpleName() + '-' + id;
            removeFromCache(key);
        }
    }

    public void update(Object obj) {
        simpleJdbcTemplate.update(orm.getUpdateSql(obj.getClass()), new BeanPropertySqlParameterSource(obj));
        if (orm.needCache(obj.getClass())) {
            removeFromCache(obj, getObjectId(obj));
        }
    }

    public <T> T findFromCache(long id, Class<T> type) {
        String key = tablePreFix+type.getSimpleName() + '-' + id;

        String v = (String) cacheClient.get(key);
        if (v != null) {
            return JsonUtils.fromJson(v, type);
        }

        return null;
    }

    public <T> void putToCache(T obj, Class<T> type, long id) {
        String key = type.getSimpleName() + '-' + id;
        cacheClient.set(key, JsonUtils.toJson(obj));
    }

    public void removeFromCache(Object obj, long id) {
        String key = obj.getClass().getSimpleName() + '-' + id;
        cacheClient.delete(key);
    }

    public void removeFromCache(String key) {
        cacheClient.delete(key);
    }

    public <T> T findFromDb(long id, Class<T> type) {
        BeanPropertyRowMapper<T> rm = new BeanPropertyRowMapper<T>(type);
        rm.setPrimitivesDefaultedForNullValue(true);

        String sql = "select * from " + tablePreFix + type.getSimpleName() + " where " + type.getSimpleName() + "id = ?";
        return simpleJdbcTemplate.queryForObject(sql, rm, id);
    }

    public <T> T findFirst(Class<T> type, String sql, Object... args) {
        BeanPropertyRowMapper<T> rm = new BeanPropertyRowMapper<T>(type);
        rm.setPrimitivesDefaultedForNullValue(true);

        if (database.equalsIgnoreCase("MySQL")) {
            String _sql = sql + " limit 1";
            try {
                return simpleJdbcTemplate.queryForObject(_sql, rm, args);
            } catch (EmptyResultDataAccessException ex) {
                return null;
            }
        }
        if (database.equalsIgnoreCase("Oracle")) {
            String _sql =
                    "select * from (select A.*, rownum as r__n from ( "
                    + sql
                    + ") A) B where B.r__n = 1";
            try {
                return simpleJdbcTemplate.queryForObject(_sql, rm, args);
            } catch (EmptyResultDataAccessException ex) {
                return null;
            }
        }
        throw new IllegalStateException("Datebase not supported !" + database);
    }

    public <T> List<T> page(Class<T> type, String sql, int pageNo, int pageSize, Map<String, Object> paramMap) {
        SqlPageBuilder pageBuilder = mySQLPageBuilder;
        if (database.equalsIgnoreCase("Oracle")) {
            pageBuilder = oraclePageBuilder;
        }

        if (orm.needCache(type)) {
            List<Long> idList = simpleJdbcTemplate.query(
                    pageBuilder.buildPageSql(sql, pageNo, pageSize),
                    idRowMapper, paramMap);
            List<T> result = new ArrayList<T>();
            for (Long id : idList) {
                result.add(find(id, type));
            }
            return result;
        } else {
            BeanPropertyRowMapper<T> rm = new BeanPropertyRowMapper<T>(type);
            rm.setPrimitivesDefaultedForNullValue(true);
            return simpleJdbcTemplate.query(
                    pageBuilder.buildPageSql(sql, pageNo, pageSize),
                    rm, paramMap);
        }
    }

    public int count(String sql, Map<String, Object> paramMap) {
        return simpleJdbcTemplate.queryForInt(sql, paramMap);
    }

    public int count(String sql, Object... args) {
        return simpleJdbcTemplate.queryForInt(sql, args);
    }

    public <T> List<T> list(Class<T> type, String sql, Object... args) {
        if (orm.needCache(type)) {
            List<Long> idList = simpleJdbcTemplate.query(sql, idRowMapper, args);
            List<T> result = new ArrayList<T>();
            for (Long id : idList) {
                result.add(find(id, type));
            }
            return result;
        } else {
            BeanPropertyRowMapper<T> rm = new BeanPropertyRowMapper<T>(type);
            rm.setPrimitivesDefaultedForNullValue(true);
            return simpleJdbcTemplate.query(sql, rm, args);
        }
    }

    public <T> List<T> list(Class<T> type, String sql, Map<String, Object> paramMap) {
        if (orm.needCache(type)) {
            List<Long> idList = simpleJdbcTemplate.query(sql, idRowMapper, paramMap);
            List<T> result = new ArrayList<T>();
            for (Long id : idList) {
                result.add(find(id, type));
            }
            return result;
        } else {
            BeanPropertyRowMapper<T> rm = new BeanPropertyRowMapper<T>(type);
            rm.setPrimitivesDefaultedForNullValue(true);
            return simpleJdbcTemplate.query(sql, rm, paramMap);
        }
    }
    public static RowMapper<Long> idRowMapper = new RowMapper<Long>() {

        public Long mapRow(ResultSet rs, int i) throws SQLException {
            return rs.getLong(1);
        }
    };

    public int tryUpdate(String sql, Object... args) {
        return simpleJdbcTemplate.update(sql, args);
    }

    public int tryUpdate(String sql, Map<String, Object> paramMap) {
        return simpleJdbcTemplate.update(sql, paramMap);
    }

    public long getDBTimestamp() {
        if (database.equalsIgnoreCase("Oracle")) {
            String sql = " select sysdate from dual ";
            List<java.sql.Timestamp> list = simpleJdbcTemplate.query(sql, tsRowMapper);
            return list.get(0).getTime();
        } else {
            return 0;
        }
    }
    public static RowMapper<java.sql.Timestamp> tsRowMapper = new RowMapper<java.sql.Timestamp>() {

        public java.sql.Timestamp mapRow(ResultSet rs, int i) throws SQLException {
            return rs.getTimestamp(1);
        }
    };
}
